﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.IO;
using TeachMe.Application.Exceptions;

namespace TeachMe.Application.Themes
{
    public class ThemeLoader
    {
        private class ThemeLoaderContext
        {
            public XmlEditor XmlEditor { get; set; }
            public string ThemeContentDirectory { get; set; }

            public string CreatePath(string fileName)
            {
                return Path.Combine(ThemeContentDirectory, fileName);
            }
        }

        private static readonly PrivateFontCollection PrivateFonts = new PrivateFontCollection();
        private const string ThemeDescriptorFileName = "theme.xml";

        public static Theme LoadFromArchive(TemporaryContentManager temporaryContentManager, string archive)
        {
            string directory = temporaryContentManager.CreateTempDirectory();

            try
            {
                Utilities.Unzip(directory, archive);

                return Load(directory, archive);
            }
            finally
            {
                if (Directory.Exists(directory))
                {
                    // TODO: delete temporary directory
                }
            }
        }

        public static Theme LoadFromStream(TemporaryContentManager temporaryContentManager, Stream stream)
        {
            string directory = temporaryContentManager.CreateTempDirectory();
            directory = Utilities.Unzip(directory, stream);
            
            return Load(directory, null);
        }

        private static Theme Load(string themeDirectory, string originalPath)
        {
            ThemeLoaderContext context = new ThemeLoaderContext {ThemeContentDirectory = themeDirectory};

            try
            {
                try
                {
                    context.XmlEditor = XmlEditor.Load(Path.Combine(context.ThemeContentDirectory, ThemeDescriptorFileName));
                }
                catch (XmlEditorException e)
                {
                    throw new ThemeException("Theme descriptor could not be loaded.", e);
                }

                if (!context.XmlEditor.CheckVersion(Theme.Version))
                {
                    throw new ThemeException("Invalid theme version.");
                }

                Theme result = new Theme();

                result.Name = context.XmlEditor.GetAttributeValue("name");
                if (context.XmlEditor.HasAttributeValue("author"))
                {
                    result.Author = context.XmlEditor.GetAttributeValue("author");
                }

                result.Path = originalPath;

                using (context.XmlEditor.LoadNode("Background"))
                {
                    result.Background = new FragmentedImage();

                    PopulateFragmentedImage(context, result.Background);
                }

                using (context.XmlEditor.LoadNode("Progress"))
                {
                    result.Progress = new Progress();

                    PopulateFragmentedImage(context, result.Progress);

                    using (context.XmlEditor.LoadNode("Align"))
                    {
                        result.Progress.Align = context.XmlEditor.GetEnumValue<ProgressAlign>();
                    }
                }

                result.Buttons = LoadButtons(context);
                result.Texts = LoadTexts(context);

                using (context.XmlEditor.LoadNode("TextsVerticalDistance"))
                {
                    result.TextsVerticalDistance = context.XmlEditor.GetIntValue();
                }

                return result;
            }
            finally
            {
                if (Directory.Exists(context.ThemeContentDirectory))
                {
                    // TODO: check what is still opened
                    // Directory.Delete(context.ThemeContentDirectory, true);
                }
            }
        }

        private static List<Text> LoadTexts(ThemeLoaderContext context)
        {
            List<Text> result = new List<Text>();

            using (context.XmlEditor.LoadNode("Texts"))
            {
                foreach (IDisposable disposable in context.XmlEditor.LoadChildNodes())
                {
                    Text text = new Text();

                    using (disposable)
                    {
                        text.Font = LoadFont(context);

                        using (context.XmlEditor.LoadNode("Type"))
                        {
                            text.Type = context.XmlEditor.GetEnumValue<EntryTextType>();
                        }

                        using (context.XmlEditor.LoadNode("Color"))
                        {
                            text.Color = context.XmlEditor.GetColorValue();
                        }
                    }

                    result.Add(text);
                }
            }

            return result;
        }

        private static Font LoadFont(ThemeLoaderContext context)
        {
            using (context.XmlEditor.LoadNode("Font"))
            {
                string fontFamilyName;
                FontStyle fontStyle;
                float size;

                bool userDefinedFont = false;

                if (context.XmlEditor.HasChildNode("File"))
                {
                    using (context.XmlEditor.LoadNode("File"))
                    {
                        userDefinedFont = true;

                        string file = context.CreatePath(context.XmlEditor.GetTextValue());

                        if (File.Exists(file))
                        {
                            // it does not load same font twice
                            PrivateFonts.AddFontFile(file);
                        }
                    }
                }

                using (context.XmlEditor.LoadNode("Family"))
                {
                    fontFamilyName = context.XmlEditor.GetTextValue();
                }

                using (context.XmlEditor.LoadNode("FontStyle"))
                {
                    fontStyle = context.XmlEditor.GetEnumValue<FontStyle>();
                }

                using (context.XmlEditor.LoadNode("Size"))
                {
                    size = context.XmlEditor.GetFloatValue();
                }

                foreach (FontFamily fontFamily in PrivateFonts.Families)
                {
                    if (String.Compare(fontFamily.Name, fontFamilyName, true) == 0)
                    {
                        return new Font(fontFamily, size, fontStyle);
                    }
                }

                if (userDefinedFont)
                {
                    throw new ThemeException(String.Format("Font family name '{0}' does not exist.", fontFamilyName));
                }

                return new Font(fontFamilyName, size, fontStyle);
            }
        }

        private static List<Button> LoadButtons(ThemeLoaderContext context)
        {
            List<Button> result = new List<Button>();

            using (context.XmlEditor.LoadNode("Buttons"))
            {
                foreach (IDisposable disposable in context.XmlEditor.LoadChildNodes())
                {
                    Button button = new Button();

                    using (disposable)
                    {
                        using (context.XmlEditor.LoadNode("Rectangle"))
                        {
                            button.Rectangle = LoadRectangle(context);
                        }

                        using (context.XmlEditor.LoadNode("Type"))
                        {
                            button.Type = context.XmlEditor.GetEnumValue<EntryButtonType>();
                        }

                        button.Images = LoadButtonImages(context);
                    }

                    result.Add(button);
                }
            }

            return result;
        }

        private static List<ButtonImage> LoadButtonImages(ThemeLoaderContext context)
        {
            List<ButtonImage> result = new List<ButtonImage>();

            using (context.XmlEditor.LoadNode("Images"))
            {
                foreach (IDisposable disposable in context.XmlEditor.LoadChildNodes())
                {
                    ButtonImage buttonImage = new ButtonImage();

                    using (disposable)
                    {
                        using (context.XmlEditor.LoadNode("Type"))
                        {
                            buttonImage.Type = context.XmlEditor.GetEnumValue<ButtonImageType>();
                        }

                        using (context.XmlEditor.LoadNode("File"))
                        {
                            buttonImage.Image = Image.FromFile(context.CreatePath(context.XmlEditor.GetTextValue()));
                        }
                    }

                    result.Add(buttonImage);
                }
            }

            return result;
        }

        private static void PopulateFragmentedImage(ThemeLoaderContext context, FragmentedImage fragmentedImage)
        {
            using (context.XmlEditor.LoadNode("File"))
            {
                fragmentedImage.Image = Image.FromFile(context.CreatePath(context.XmlEditor.GetTextValue()));
            }

            using (context.XmlEditor.LoadNode("ExtendMode"))
            {
                fragmentedImage.ExtendMode = context.XmlEditor.GetEnumValue<ImageExtendMode>();
            }

            using (context.XmlEditor.LoadNode("Regions"))
            {
                fragmentedImage.Regions = new List<Region>();

                foreach (IDisposable disposable in context.XmlEditor.LoadChildNodes())
                {
                    using (disposable)
                    {
                        Region region = new Region();

                        using (context.XmlEditor.LoadNode("Type"))
                        {
                            region.Type = context.XmlEditor.GetEnumValue<RegionType>();
                        }

                        using (context.XmlEditor.LoadNode("Rectangle"))
                        {
                            region.Rectangle = LoadRectangle(context);
                        }

                        fragmentedImage.Regions.Add(region);
                    }
                }
            }
        }

        private static Rectangle LoadRectangle(ThemeLoaderContext context)
        {
            int left, top, height, width;

            using (context.XmlEditor.LoadNode("Left"))
            {
                left = context.XmlEditor.GetIntValue();
            }
            using (context.XmlEditor.LoadNode("Top"))
            {
                top = context.XmlEditor.GetIntValue();
            }
            using (context.XmlEditor.LoadNode("Height"))
            {
                height = context.XmlEditor.GetIntValue();
            }
            using (context.XmlEditor.LoadNode("Width"))
            {
                width = context.XmlEditor.GetIntValue();
            }

            return new Rectangle(left, top, width, height);
        }
    }
}
